package org.nobody.mallb2c.mallorder.config;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;

/**
 * @author 3489309323@qq.com
 * @create 2020-09-20-20:45
 */
@Configuration
public class MyRabbitConfig {
  @Autowired
  RabbitTemplate rabbitTemplate;

  @Bean
  public MessageConverter messageConverter(){
    return new Jackson2JsonMessageConverter();
  }

  /**
   * 定制RabbitTemplate, 保证消息队列的可靠投递
   *
   * 1、服务受到消息就回调
   *   ① spring.rabbitmq.publisher-confirms=true
   *   ② 设置确认回调 rabbitTemplate.setConfirmCallback
   * 2、消息正确抵达队列进行回调
   *   ① spring.rabbitmq.publisher-returns=true
   *      spring.rabbitmq.template.mandatory=true
   *   ② 设置确认回调 rabbitTemplate.setReturnCallback
   * 3、 消费端确认(保证每个消息被正确消费,此时才可以broker删除这个消息)
   *   ① 默认是自动确认的,只要消息接收到,客户端会自动确认,服务端就会移除这个消息
   *      问题:
   *          我们收到很多消息,自动回复给服务器ack(在一个@RabbitHandler方法执行完毕后)
   *          ,但如果在接受到消息后,业务处理一半,突然宕机了,那么此时消息其实就相当于丢失了(broker已经接受到自动回复的ack)
   *
   *          So 我们可以进行手动确认来避免这个问题
   *   ② 手动确认 spring.rabbitmq.listener.simple.acknowledge-mode=manual
   *      只要我们没有明确告诉MQ 货物被签收 即没有Ack。
   *      那么消息就一直是unacked状态
   *      即使Consumer宕机,消息也不会丢失
   *      会重新编为Ready,下一次有新的Consumer连接进来,会发给它
   *
   *      如何签收?(业务成功就该签收
   *      channel.basicAck(deliveryTag, false ←只签收当前这个货物,做一个确认一个)
   *      如何拒签?(业务失败就该拒签
   *      channel.basicNack(deliveryTag,false,true ←如果为true会重新发回MQ重新入队,如果为false会直接丢弃);
   *
   */
  @PostConstruct //MyRabbitConfig 构造器调用以后再调用以下方法
  public void initRabbitTemplate(){
    //设置确认回调
    rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
      /**
       * @param correlationData 当前消息的唯一关联数据(这个消息的唯一id)
       * @param ack 消息是否成功收到
       * @param cause 失败的原因
       */
      @Override
      public void confirm(CorrelationData correlationData/*←←消息的唯一id,如果没传,则为null*/, boolean ack, String cause) {
        //rabbitTemplate.convertAndSend("hello-java-exchange","hello222.java",orderEntity,/*→ 可选,指定消息的唯一id*/ new CorrelationData(UUID.randomUUID().toString()));
        //↑ 可以对唯一id做标识 比如 mysql-xxx(xxx才是真正的唯一标识), 这样就知道此消息一定要确保可靠性, 我们这里需要反向给客户端发回一个消息标识已经收到消息, 这样客户端才会调用数据库修改这条消息的状态, 表示消息已到达mq

        System.out.println("confirm...correlationData[" + correlationData + "] ===>ack[" + ack + "] ===>cause[" + cause + "]");
      }
    });

    rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
      /**
       * 只有消息没有投递给指定的队列,才会触发失败回调
       * @param message 投递失败的消息详细类型
       * @param replyCode 回复的状态码
       * @param replyText 回复的文本内容
       * @param exchange 当时这个消息发给哪个交换机
       * @param routingKey 当时这个消息用哪个路由键
       */
      @Override
      public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
        System.out.println("Fail Message[" + message + "] ===>replyCode[" + replyCode + "] ===>replyText[" + replyText + "] ===>exchange[" + exchange + "] ===>routingKey[" + routingKey + "]");

        //如果消息到达mq,但没有成功投递给队列,这里也可以再给客户端发送消息,让它将数据库里的那条消息的状态重新改回未投递
      }
    });
  }
}
